package org.codeanalyzer.report;

import java.io.FileWriter;
import java.io.Writer;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.codeanalyzer.model.ApiClass;
import org.codeanalyzer.model.ApiLibrary;
import org.codeanalyzer.model.ApiMethod;
import org.codeanalyzer.model.ApiPackage;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * Creates usage report *.xml file in format of EMMA code coverage tool
 */
class EmmaXmlReportBuilder extends UsageReportBuilder
{
    @Override
    public void printReport(ApiLibrary apiLibrary, long elapsedTime, int numOfFiles) {
        String fileName = System.getProperty("report.file", apiLibrary.getName() + ".xml");
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            Document doc = factory.newDocumentBuilder().newDocument();
            addElementReport(doc, apiLibrary);

            FileWriter fileWriter = new FileWriter(fileName);
            try {
                writeDocument(doc, fileWriter);
            }
            finally {
                fileWriter.close();
            }
            System.out.println("Report is saved to the file " + fileName);
        }
        catch (Exception e) {
            throw new RuntimeException("Unable to save API library data to the file " + fileName, e);
        }
    }

    private void addElementReport(Document doc, ApiLibrary apiLibrary)
    {
        Element reportElement = doc.createElement("report");
        doc.appendChild(reportElement);

        addElementStats(reportElement, apiLibrary);

        Element dataElement = createChild(reportElement, "data");
        addElementAll(dataElement, apiLibrary);
    }

    private void addElementAll(Element dataElement, ApiLibrary apiLibrary) {
        Element allElement = createChild(dataElement, "all");
        allElement.setAttribute("name", "all classes");

        addCoverageElem(allElement, "class", apiLibrary.getTotalCoveredClasses(), apiLibrary.getTotalClasses());
        addCoverageElem(allElement, "method", apiLibrary.getTotalCoveredMethods(), apiLibrary.getTotalMethods());
        addCoverageElem(allElement, "block", 0, 0);
        addCoverageElem(allElement, "line", 0, 0);

        for (ApiPackage apiPackage : apiLibrary.getPackages()) {
            addElementPackage(allElement, apiPackage);
        }
    }

    private void addCoverageElem(Element element, String type, int covered, int total) {
        Element coverageElement = createChild(element, "coverage");

        coverageElement.setAttribute("type", type + ", %");
        coverageElement.setAttribute("value", formattedCoverage(covered, total));
    }

    static String formattedCoverage(int covered, int total) {
        if (total == 0)
            return "0%   (0/0)";
        return String.format("%d%% (%d/%d)", (int) ((double) covered / total * 100), covered, total);
    }

    private void addElementStats(Element reportElement, ApiLibrary apiLibrary)
    {
        Element statsElement = createChild(reportElement, "stats");
        addElementStatsChild(statsElement, "packages", apiLibrary.getTotalPackages());
        addElementStatsChild(statsElement, "classes", apiLibrary.getTotalClasses());
        addElementStatsChild(statsElement, "methods", apiLibrary.getTotalMethods());
        addElementStatsChild(statsElement, "srcfiles", apiLibrary.getTotalClasses());
        addElementStatsChild(statsElement, "srclines", 0);
    }

    private void addElementStatsChild(Element statsElement, String name, int value) {
        Element element = createChild(statsElement, name);
        element.setAttribute("value", String.valueOf(value));
    }

    private Element createChild(Element parent, String name) {
        Element element = parent.getOwnerDocument().createElement(name);
        parent.appendChild(element);
        return element;
    }

    private void addElementPackage(Element reportElement, ApiPackage apiPackage)
    {
        Element packageElement = createChild(reportElement, "package");
        packageElement.setAttribute("name", apiPackage.getName());
        addCoverageElem(packageElement, "class", apiPackage.getTotalCoveredClasses(), apiPackage.getTotalClasses());
        addCoverageElem(packageElement, "method", apiPackage.getTotalCoveredMethods(), apiPackage.getTotalMethods());
        addCoverageElem(packageElement, "block", apiPackage.isCovered() ? 1 : 0, 1);
        addCoverageElem(packageElement, "line", apiPackage.isCovered() ? 1 : 0, 1);

        for (ApiClass apiClass : apiPackage.getClasses())
        {
            addElementClass(packageElement, apiClass);
        }
    }

    private void addElementClass(Element packageElement, ApiClass apiClass)
    {
        Element srcfileElement = createChild(packageElement, "srcfile");
        srcfileElement.setAttribute("name", apiClass.getSimpleName() + ".java");
        addCoverageElem(srcfileElement, "class", apiClass.isCovered() ? 1 : 0, 1);
        addCoverageElem(srcfileElement, "method", apiClass.getTotalCoveredMethods(), apiClass.getTotalMethods());
        addCoverageElem(srcfileElement, "block", apiClass.isCovered() ? 1 : 0, 1);
        addCoverageElem(srcfileElement, "line", apiClass.isCovered() ? 1 : 0, 1);

        Element classElement = createChild(srcfileElement, "class");
        classElement.setAttribute("name", apiClass.getSimpleName());
        addCoverageElem(classElement, "class", apiClass.isCovered() ? 1 : 0, 1);
        addCoverageElem(classElement, "method", apiClass.getTotalCoveredMethods(), apiClass.getTotalMethods());
        addCoverageElem(classElement, "block", apiClass.isCovered() ? 1 : 0, 1);
        addCoverageElem(classElement, "line", apiClass.isCovered() ? 1 : 0, 1);

        for (ApiMethod apiMethod : apiClass.getMethods())
        {
            addElementMethod(classElement, apiMethod);
        }
    }

    private void addElementMethod(Element classElement, ApiMethod apiMethod) {
        Element methodElement = createChild(classElement, "method");
        methodElement.setAttribute("name", apiMethod.getSimpleName());
        addCoverageElem(methodElement, "method", apiMethod.isCovered() ? 1 : 0, 1);
        addCoverageElem(methodElement, "block", apiMethod.isCovered() ? 1 : 0, 1);
        addCoverageElem(methodElement, "line", apiMethod.isCovered() ? 1 : 0, 1);
    }

    private void writeDocument(Document doc, Writer writer) throws TransformerException
    {
        DOMSource domSource = new DOMSource(doc);
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        transformer.setOutputProperty(OutputKeys.METHOD, "xml");
        transformer.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1");
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");

        StreamResult sr = new StreamResult(writer);
        transformer.transform(domSource, sr);
    }
}
